// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <X11/Xlib.h>
#include <X11/keysym.h>

// X macro fail.
#if defined(RootWindow)
#undef RootWindow
#endif

#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/test/aura_test_utils.h"
#include "ui/aura/test/ui_controls_factory_aura.h"
#include "ui/aura/test/x11_event_sender.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/test/ui_controls_aura.h"
#include "ui/base/x/x11_util.h"
#include "ui/compositor/dip_util.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/events/test/platform_event_waiter.h"
#include "ui/gfx/x/x11_connection.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"

namespace views {
namespace test {
    namespace {

        using ui_controls::DOWN;
        using ui_controls::LEFT;
        using ui_controls::MIDDLE;
        using ui_controls::MouseButton;
        using ui_controls::RIGHT;
        using ui_controls::UIControlsAura;
        using ui_controls::UP;

        // Mask of the buttons currently down.
        unsigned button_down_mask = 0;

        // Returns atom that indidates that the XEvent is marker event.
        Atom MarkerEventAtom()
        {
            return XInternAtom(gfx::GetXDisplay(), "marker_event", False);
        }

        // Returns true when the event is a marker event.
        bool Matcher(const base::NativeEvent& event)
        {
            return event->xany.type == ClientMessage && event->xclient.message_type == MarkerEventAtom();
        }

        class UIControlsDesktopX11 : public UIControlsAura {
        public:
            UIControlsDesktopX11()
                : x_display_(gfx::GetXDisplay())
                , x_root_window_(DefaultRootWindow(x_display_))
                , x_window_(XCreateWindow(
                      x_display_, x_root_window_,
                      -100, -100, 10, 10, // x, y, width, height
                      0, // border width
                      CopyFromParent, // depth
                      InputOnly,
                      CopyFromParent, // visual
                      0,
                      NULL))
            {
                XStoreName(x_display_, x_window_, "Chromium UIControlsDesktopX11 Window");
            }

            ~UIControlsDesktopX11() override { XDestroyWindow(x_display_, x_window_); }

            bool SendKeyPress(gfx::NativeWindow window,
                ui::KeyboardCode key,
                bool control,
                bool shift,
                bool alt,
                bool command) override
            {
                DCHECK(!command); // No command key on Aura
                return SendKeyPressNotifyWhenDone(
                    window, key, control, shift, alt, command, base::Closure());
            }

            bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
                ui::KeyboardCode key,
                bool control,
                bool shift,
                bool alt,
                bool command,
                const base::Closure& closure) override
            {
                DCHECK(!command); // No command key on Aura

                aura::WindowTreeHost* host = window->GetHost();

                XEvent xevent = { 0 };
                xevent.xkey.type = KeyPress;
                if (control) {
                    SetKeycodeAndSendThenMask(host, &xevent, XK_Control_L, ControlMask);
                }
                if (shift)
                    SetKeycodeAndSendThenMask(host, &xevent, XK_Shift_L, ShiftMask);
                if (alt)
                    SetKeycodeAndSendThenMask(host, &xevent, XK_Alt_L, Mod1Mask);
                xevent.xkey.keycode = XKeysymToKeycode(x_display_,
                    ui::XKeysymForWindowsKeyCode(key, shift));
                aura::test::PostEventToWindowTreeHost(xevent, host);

                // Send key release events.
                xevent.xkey.type = KeyRelease;
                aura::test::PostEventToWindowTreeHost(xevent, host);
                if (alt)
                    UnmaskAndSetKeycodeThenSend(host, &xevent, Mod1Mask, XK_Alt_L);
                if (shift)
                    UnmaskAndSetKeycodeThenSend(host, &xevent, ShiftMask, XK_Shift_L);
                if (control) {
                    UnmaskAndSetKeycodeThenSend(host, &xevent, ControlMask, XK_Control_L);
                }
                DCHECK(!xevent.xkey.state);
                RunClosureAfterAllPendingUIEvents(closure);
                return true;
            }

            bool SendMouseMove(long screen_x, long screen_y) override
            {
                return SendMouseMoveNotifyWhenDone(screen_x, screen_y, base::Closure());
            }
            bool SendMouseMoveNotifyWhenDone(long screen_x,
                long screen_y,
                const base::Closure& closure) override
            {
                gfx::Point screen_location(screen_x, screen_y);
                gfx::Point root_location = screen_location;
                aura::Window* root_window = RootWindowForPoint(screen_location);

                aura::client::ScreenPositionClient* screen_position_client = aura::client::GetScreenPositionClient(root_window);
                if (screen_position_client) {
                    screen_position_client->ConvertPointFromScreen(root_window,
                        &root_location);
                }

                aura::WindowTreeHost* host = root_window->GetHost();
                gfx::Point root_current_location = aura::test::QueryLatestMousePositionRequestInHost(host);
                host->ConvertPointFromHost(&root_current_location);

                if (root_location != root_current_location && button_down_mask == 0) {
                    // Move the cursor because EnterNotify/LeaveNotify are generated with the
                    // current mouse position as a result of XGrabPointer()
                    root_window->MoveCursorTo(root_location);
                } else {
                    XEvent xevent = { 0 };
                    XMotionEvent* xmotion = &xevent.xmotion;
                    xmotion->type = MotionNotify;
                    xmotion->x = root_location.x();
                    xmotion->y = root_location.y();
                    xmotion->state = button_down_mask;
                    xmotion->same_screen = True;
                    // RootWindow will take care of other necessary fields.
                    aura::test::PostEventToWindowTreeHost(xevent, host);
                }
                RunClosureAfterAllPendingUIEvents(closure);
                return true;
            }
            bool SendMouseEvents(MouseButton type, int state) override
            {
                return SendMouseEventsNotifyWhenDone(type, state, base::Closure());
            }
            bool SendMouseEventsNotifyWhenDone(MouseButton type,
                int state,
                const base::Closure& closure) override
            {
                XEvent xevent = { 0 };
                XButtonEvent* xbutton = &xevent.xbutton;
                gfx::Point mouse_loc = aura::Env::GetInstance()->last_mouse_location();
                aura::Window* root_window = RootWindowForPoint(mouse_loc);
                aura::client::ScreenPositionClient* screen_position_client = aura::client::GetScreenPositionClient(root_window);
                if (screen_position_client)
                    screen_position_client->ConvertPointFromScreen(root_window, &mouse_loc);
                xbutton->x = mouse_loc.x();
                xbutton->y = mouse_loc.y();
                xbutton->same_screen = True;
                switch (type) {
                case LEFT:
                    xbutton->button = Button1;
                    xbutton->state = Button1Mask;
                    break;
                case MIDDLE:
                    xbutton->button = Button2;
                    xbutton->state = Button2Mask;
                    break;
                case RIGHT:
                    xbutton->button = Button3;
                    xbutton->state = Button3Mask;
                    break;
                }
                // RootWindow will take care of other necessary fields.
                if (state & DOWN) {
                    xevent.xbutton.type = ButtonPress;
                    aura::test::PostEventToWindowTreeHost(xevent, root_window->GetHost());
                    button_down_mask |= xbutton->state;
                }
                if (state & UP) {
                    xevent.xbutton.type = ButtonRelease;
                    aura::test::PostEventToWindowTreeHost(xevent, root_window->GetHost());
                    button_down_mask = (button_down_mask | xbutton->state) ^ xbutton->state;
                }
                RunClosureAfterAllPendingUIEvents(closure);
                return true;
            }
            bool SendMouseClick(MouseButton type) override
            {
                return SendMouseEvents(type, UP | DOWN);
            }
            void RunClosureAfterAllPendingUIEvents(
                const base::Closure& closure) override
            {
                if (closure.is_null())
                    return;
                static XEvent* marker_event = NULL;
                if (!marker_event) {
                    marker_event = new XEvent();
                    marker_event->xclient.type = ClientMessage;
                    marker_event->xclient.display = x_display_;
                    marker_event->xclient.window = x_window_;
                    marker_event->xclient.format = 8;
                }
                marker_event->xclient.message_type = MarkerEventAtom();
                XSendEvent(x_display_, x_window_, False, 0, marker_event);
                ui::PlatformEventWaiter::Create(closure, base::Bind(&Matcher));
            }

        private:
            aura::Window* RootWindowForPoint(const gfx::Point& point)
            {
                // Most interactive_ui_tests run inside of the aura_test_helper
                // environment. This means that we can't rely on gfx::Screen and several
                // other things to work properly. Therefore we hack around this by
                // iterating across the windows owned DesktopWindowTreeHostX11 since this
                // doesn't rely on having a DesktopScreenX11.
                std::vector<aura::Window*> windows = DesktopWindowTreeHostX11::GetAllOpenWindows();
                for (std::vector<aura::Window*>::const_iterator it = windows.begin();
                     it != windows.end(); ++it) {
                    if ((*it)->GetBoundsInScreen().Contains(point)) {
                        return (*it)->GetRootWindow();
                    }
                }

                NOTREACHED() << "Coulding find RW for " << point.ToString() << " among "
                             << windows.size() << " RWs.";
                return NULL;
            }

            void SetKeycodeAndSendThenMask(aura::WindowTreeHost* host,
                XEvent* xevent,
                KeySym keysym,
                unsigned int mask)
            {
                xevent->xkey.keycode = XKeysymToKeycode(x_display_, keysym);
                aura::test::PostEventToWindowTreeHost(*xevent, host);
                xevent->xkey.state |= mask;
            }

            void UnmaskAndSetKeycodeThenSend(aura::WindowTreeHost* host,
                XEvent* xevent,
                unsigned int mask,
                KeySym keysym)
            {
                xevent->xkey.state ^= mask;
                xevent->xkey.keycode = XKeysymToKeycode(x_display_, keysym);
                aura::test::PostEventToWindowTreeHost(*xevent, host);
            }

            // Our X11 state.
            Display* x_display_;
            ::Window x_root_window_;

            // Input-only window used for events.
            ::Window x_window_;

            DISALLOW_COPY_AND_ASSIGN(UIControlsDesktopX11);
        };

    } // namespace

    UIControlsAura* CreateUIControlsDesktopAura()
    {
        // The constructor of UIControlsDesktopX11 needs X11 connection to be
        // initialized.
        gfx::InitializeThreadedX11();
        return new UIControlsDesktopX11();
    }

} // namespace test
} // namespace views
